import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

/*
 * Class Lexer
 * 
 * Update the method nextToken() such to the provided
 * specifications of the Decaf Programming Language.
 * 
 * You are not allowed to use any built it in tokenizer
 * in Java. You are only allowed to scan the input file
 * one character at a time.
 */

public class Lexer {

	private BufferedReader reader; // Reader
	private char curr; // The current character being scanned
	static int currLine = 1;
	static int currChar = 0;
	private static final char EOF = (char) (-1);

	// End of file character

	public Lexer(String file) {
		try {
			reader = new BufferedReader(new FileReader(file));
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}

		// Read the first character
		curr = read();
	}

	private char read() {
		try {
			char c = (char) (reader.read());
			currChar++;
			return c;
		} catch (IOException e) {
			e.printStackTrace();
			return EOF;
		}
	}

	// Checks if a character is a digit
	private boolean isNumeric(char c) {
		if (c >= '0' && c <= '9')
			return true;

		return false;
	}

	public boolean isLetterOr_(char c) {
		return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_';
	}

	public boolean isDigit(char c) {
		return c >= '0' && c <= '9';
	}

	public Token nextToken() {
		int state = 1; // Initial state
		String numBuffer = ""; // A buffer for number literals
		String strBuffer = "";
		while (true) {
			if (curr == EOF) {
				try {
					reader.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
				return new Token(Token.EOF, "");
			}
			// System.out.println(curr);
			switch (state) {
			// Controller
			case 1:
				switch (curr) {
				case ' ':
				case '\n':
					incremeantLine();
				case '\r':
				case '\b':
				case '\f':
				case '\t':
					curr = read();
					continue;

				case ';':
					curr = read();
					return new Token(Token.SM, ";");

				case '+':
					curr = read();
					return new Token(Token.PO, "+");

				case '-':
					curr = read();
					return new Token(Token.MO, "-");

				case '*':
					curr = read();
					return new Token(Token.TO, "*");

				case '=':
					curr = read();
					if (curr == '=') {
						curr = read();
						return new Token(Token.EQ, "==");
					} else
						return new Token(Token.AO, "=");

				case '!':
					curr = read();
					curr = read();
					return new Token(Token.NE, "!=");

				case '|':
					curr = read();
					curr = read();
					return new Token(Token.LO, "||");

				case '&':
					curr = read();
					curr = read();
					return new Token(Token.LA, "&&");

				case '%':
					curr = read();
					return new Token(Token.MD, "%");

					// ----------- Comments Part :
				case '/':
					curr = read();
					if (curr != '/' && curr != '*' && curr != '\n') {
						// System.out.println("WEIRDO " + curr);
						return new Token(Token.DO, "/");
					} else if (curr == '/') {
						state = 8;
						curr = read();
					} else if (curr == '*') {
						state = 9;
						curr = read();
					}
					continue;
				case ',':
					curr = read();
					return new Token(Token.FA, ",");

				case '{':
					curr = read();
					return new Token(Token.LB, "{");

				case '}':
					curr = read();
					return new Token(Token.RB, "}");

				case '(':
					curr = read();
					return new Token(Token.LP, "(");

				case ')':
					curr = read();
					return new Token(Token.RP, ")");

				default:
					state = 2; // Check the next possibility
					continue;
				}

				// Integer - Start
			case 2:
				if (isNumeric(curr)) {
					numBuffer = ""; // Reset the buffer.
					numBuffer += (curr - '0');

					state = 3;
					curr = read();
				} else {
					state = 4;
				}

				continue;

				// Integer - Body
			case 3:
				if (isNumeric(curr)) {

					numBuffer += curr;
					curr = read();

				} else if (curr == '.') {

					curr = read();
					numBuffer += "." + (curr);
					curr = read();
				} else
					return new Token(Token.NM, "" + numBuffer);

				continue;
				// String - Start
			case 4:
				if (curr == '"') {

					strBuffer = "";
					strBuffer = curr + "";
					state = 5;
					curr = read();

				} else {
					state = 6;
				}
				continue;

				// String - Body
			case 5:

				if (curr != '"') {

					// System.out.println("99   " + curr);
					strBuffer += curr;
					curr = read();
					if (curr == '\n') {
						incremeantLine();
						curr = read();
						strBuffer = "Invalid String Literal";
						return new Token(Token.ERROR, strBuffer);
					}
				} else if (curr == '"') {
					strBuffer += curr;
					curr = read();
					return new Token(Token.ST, strBuffer);
				}
				continue;

			case 6:
				if (curr != '?') { // to handle the ?23sd
					strBuffer = "";
					strBuffer = curr + "";

					state = 7;
					curr = read();
				} else {// id or keyword
					curr = read();
					return new Token(Token.ERROR, "Invalid input: ?");
				}
				continue;

			case 7: // id or kw body
				if (isLetterOr_(curr) || isDigit(curr)) {
					strBuffer += curr;
					curr = read();
				} else {
					if (strBuffer.equals("class") || strBuffer.equals("else")
							|| strBuffer.equals("if")
							|| strBuffer.equals("float")
							|| strBuffer.equals("boolean")
							|| strBuffer.equals("String")
							|| strBuffer.equals("return")
							|| strBuffer.equals("static")
							|| strBuffer.equals("while")
							|| strBuffer.equals("int")) {

						return new Token(Token.KW, strBuffer);

					} else if (strBuffer.equals("true")
							|| strBuffer.equals("false")) {
						return new Token(Token.BL, strBuffer);

					} else {
						return new Token(Token.ID, strBuffer);
					}

				}
				continue;
			case 8:
				if (curr != '\n') {
					curr = read();
				} else if (curr == '\n') {
					incremeantLine();
					curr = read();
					state = 1;

				}
				continue;
			case 9:
				if (curr != '*') {
					curr = read();
				} else if (curr == '*') {
					curr = read();
					if (curr == '/')
						state = 1;
					else
						state = 9;

				}
				// continue;
			}

		}

	}

	private void incremeantLine() {
		currLine++;
		currChar = 0;
	}
}